package cadtoolbox.utils;



import cadtoolbox.model.OligoGraph;
import cadtoolbox.model.SequenceVertex;
import cadtoolbox.optimizer.OptimizerCMAES;
import cadtoolbox.optimizer.Parameter;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.AbstractRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.chart.ChartUtilities;

import cadtoolbox.graphical.AnimatedSequences;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashSet;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;



public class PlotExpData extends JPanel {
	
	public static boolean autoplot = false;
	private final OligoGraph<SequenceVertex,String> graph;
	private XYSeriesCollection dataset;
	private XYItemRenderer renderer;
	private ChartPanel chartPanel;
	private final ArrayList<cadtoolbox.optimizer.OptimizerCMAES.ReferencePoint> pointList = new ArrayList<cadtoolbox.optimizer.OptimizerCMAES.ReferencePoint>();
	private OptimizerCMAES myOpti = null;
	private JComboBox choiceBox;
	private double[][] myExpData;
	private double[][] fullExpData;
	private AnimatedSequences as = null;
	private LinkedHashSet<SequenceVertex> plottedSeqs;
	private double[][] savedActivity;
	
    /**
     * Constructs the demo application.
     *
     * @param title  the frame title.
     */
    public PlotExpData(final String title, double expData[][], double savedActivity[][]) {
    	this(title,expData,savedActivity,1.0,1.0, null, "chart", "X", "Y", null);
    }
    

    public PlotExpData(final String title, double[][] expData, double[][] savedActivity, String[] geneNames, String xAxis, String yAxis){
    	this(title,expData,savedActivity,0.0,1.0,geneNames,"chart", xAxis, yAxis, null);
    }

    /**
     * 
     * @param title
     * @param expData
     * @param Xstart
     * @param Xstep
     * @param geneNames
     * @param fName
     * @param xAxis
     * @param yAxis
     * @param graph_
     */
    public PlotExpData(final String title, final double expData[][], double savedActivity[][], double Xstart, double Xstep, String geneNames[], String fName, String xAxis, String yAxis, OligoGraph<SequenceVertex,String> graph_) {

        //super(title);
    	this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
    	
    	this.getInputMap().put(KeyStroke.getKeyStroke("control Z"), "undo");
    	this.getActionMap().put("undo", new AbstractAction(){

			@Override
			public void actionPerformed(ActionEvent e) {
            	graph.undo();
			}

    		
    	});
    	
    	this.plottedSeqs = graph_.getPlottedSeqs();
    	LinkedHashSet<SequenceVertex> sequences = new LinkedHashSet<SequenceVertex>(graph_.getVertices()); // And I pray that the sequence order is the same ...
    	ArrayList<SequenceVertex> allseqs = new ArrayList<SequenceVertex>(sequences);
    	fullExpData = expData;
    	myExpData = new double[plottedSeqs.size()][expData[0].length];//expData.clone();
    	this.savedActivity = savedActivity;
    	Iterator<SequenceVertex> it = plottedSeqs.iterator();
    	int i=0;
    	while(it.hasNext()){
    		myExpData[i] = expData[allseqs.indexOf(it.next())];
    		i++;
    	}
    	this.graph = graph_;
        XYDataset dataset = createSampleDataset(myExpData, Xstart, Xstep, geneNames);
        JFreeChart chart = ChartFactory.createXYLineChart(
            title,
            xAxis,
            yAxis,
            dataset,
            PlotOrientation.VERTICAL,
            true,
            false,
            false
        );
        renderer = chart.getXYPlot().getRenderer();
//        XYPlot plot = (XYPlot) chart.getPlot();
        //XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
        //renderer.setSeriesLinesVisible(0, true);
        //renderer.setSeriesShapesVisible(0, true);
        //renderer.setSeriesLinesVisible(1, false);
        //renderer.setSeriesShapesVisible(1, true);        
        //plot.setRenderer(renderer);
        chartPanel = new ChartPanel(chart);
        chartPanel.setPreferredSize(new java.awt.Dimension(500, 300));
        choiceBox = new JComboBox(geneNames);
        JButton optimize = new JButton("Optimize");
        JButton cancel = new JButton("Animate");
        final PlotExpData thePlot = this;
        optimize.addActionListener(new ActionListener(){

			@Override
			public void actionPerformed(ActionEvent arg0) {
				
				ArrayList<Parameter> params = new ArrayList<Parameter>();
				
				//seqK
				Enumeration<DefaultMutableTreeNode> it = ((MutableTreeNode) graph.optimizable.getChild(graph.optimizable.getRoot(), 0)).children();
				while(it.hasMoreElements()){
					DefaultMutableTreeNode next = it.nextElement();
					if(((Parameter) next.getUserObject()).optimizable){
						params.add((Parameter) next.getUserObject());
					}
				}
				//seqC
				it = ((MutableTreeNode) graph.optimizable.getChild(graph.optimizable.getRoot(), 1)).children();
				while(it.hasMoreElements()){
					DefaultMutableTreeNode next = it.nextElement();
					if(((Parameter) next.getUserObject()).optimizable){
						params.add((Parameter) next.getUserObject());
					}
				}
				//Templates
				it = ((MutableTreeNode) graph.optimizable.getChild(graph.optimizable.getRoot(), 2)).children();
				while(it.hasMoreElements()){
					DefaultMutableTreeNode next = it.nextElement();
					if(((Parameter) next.getUserObject()).optimizable){
						params.add((Parameter) next.getUserObject());
					}
				}
				if(pointList.size()>0){
				OptimizerCMAES myOpti = new OptimizerCMAES(params, pointList, graph, thePlot);
				//myOpti.run();
				thePlot.myOpti = myOpti;
				myOpti.start();
				}
			}
        	
        });
      
        cancel.addActionListener(new ActionListener(){

			@Override
			public void actionPerformed(ActionEvent arg0) {
				animate();
			}
        	
        });
        
        final MyChartMouseListener myListener = new MyChartMouseListener(chartPanel, choiceBox);
        chartPanel.addChartMouseListener(myListener);
        //setContentPane(chartPanel);
        this.add(chartPanel);
        if(geneNames != null)
        {
        JPanel bottomPart = new JPanel();
        bottomPart.setLayout(new BoxLayout(bottomPart,BoxLayout.LINE_AXIS));
        JCheckBox addPoints = new JCheckBox();
        addPoints.setText("Add points for optimization");
        bottomPart.add(addPoints);
       
        addPoints.addActionListener(new ActionListener(){

			@Override
			public void actionPerformed(ActionEvent arg0) {
				myListener.toggle(((JCheckBox)arg0.getSource()).isSelected());
			}
        	
        });
        bottomPart.add(choiceBox);
        bottomPart.add(Box.createHorizontalGlue());
        bottomPart.add(cancel);
        bottomPart.add(optimize);
        this.add(bottomPart);
        }
//        if (fName!=null){
//        	// Save the chart
//        	try {
//        		ChartUtilities.saveChartAsJPEG(new File(fName+".jpg"), chart, 500, 300);
//        	} catch (Exception e) {
//        		System.err.println("Problem occurred saving chart.");
//        	}
//        }

    }

    
    protected void animate() {
    	if(as==null||as.myFrame==null){
    	as = new AnimatedSequences(fullExpData,savedActivity, graph);
    	}
		
	}


	private XYDataset createSampleDataset(double expData[][], double Xstart, double Xstep, String geneNames[]) {
        this.dataset = new XYSeriesCollection();
    	for (int gene=0; gene<expData.length; ++gene){
    		XYSeries series;
    		if (geneNames==null){
        		series = new XYSeries("s" + (gene+1));
        	}
        	else{
        		series = new XYSeries(geneNames[gene]);
        	}
            double t=Xstart;
        	for (int sample =0; sample<expData[gene].length; ++sample, t+=Xstep){
            	series.add(t,expData[gene][sample]);
            }
        	dataset.addSeries(series);
        }
        return dataset;
    }

    public void updatePlot(double expData[][], double savedActivity[][], double Xstart, double Xstep, String geneNames[]){
    	this.dataset.removeAllSeries();
    	this.savedActivity = savedActivity;
    	this.fullExpData = expData;
    	//LinkedHashSet<SequenceVertex> sequences = (LinkedHashSet<SequenceVertex>) graph.getVertices(); // And I pray that the sequence order is the same ...
    	ArrayList<SequenceVertex> allseqs = new ArrayList<SequenceVertex>(graph.getVertices());
    	Iterator<SequenceVertex> it = graph.getPlottedSeqs().iterator();
    	myExpData = new double[graph.getPlottedSeqs().size()][];
    	int i=0;
    	while(it.hasNext()){
    		myExpData[i] = expData[allseqs.indexOf(it.next())];
    		i++;
    	}
    	for (int gene=0; gene<myExpData.length; ++gene){
    		XYSeries series;
    		if (geneNames==null){
        		series = new XYSeries("S" + (gene+1));
        	}
        	else{
        		series = new XYSeries(geneNames[gene]);
        	}
            double t=Xstart;
        	for (int sample =0; sample<expData[gene].length; ++sample, t+=Xstep){
            	series.add(t,expData[gene][sample]);
            }
        	dataset.addSeries(series);
        	
        }
    	
    	this.repaint();
    }
    
    public void paint(Graphics g){
    	super.paint(g);
    	double x,y;
    	double domainSize = chartPanel.getChartRenderingInfo().getPlotInfo().getDataArea().getWidth();
    	double rangeSize = chartPanel.getChartRenderingInfo().getPlotInfo().getDataArea().getHeight();
    	for(cadtoolbox.optimizer.OptimizerCMAES.ReferencePoint point : pointList){
    		x = chartPanel.getChartRenderingInfo().getPlotInfo().getDataArea().getX()+(point.time - chartPanel.getChart().getXYPlot().getDomainAxis().getLowerBound())*domainSize/chartPanel.getChart().getXYPlot().getDomainAxis().getUpperBound();
    		y = chartPanel.getChartRenderingInfo().getPlotInfo().getDataArea().getY()+rangeSize-(point.target-chartPanel.getChart().getXYPlot().getRangeAxis().getLowerBound())*rangeSize/chartPanel.getChart().getXYPlot().getRangeAxis().getUpperBound();
    		Color currentColor = g.getColor();
    		
    		g.setColor((Color) this.renderer.getSeriesPaint(point.index));
    		g.fillOval((int) Math.round(x)-5, (int) Math.round(y)-5, 9, 9);
    		g.setColor(currentColor);
    	}
    }
    
    
    public void addPoint(int x, int y, int who){
    	if(chartPanel.getChartRenderingInfo().getPlotInfo().getDataArea().contains(x, y)){
			double domainSize = chartPanel.getChartRenderingInfo().getPlotInfo().getDataArea().getWidth();
			double realTime = chartPanel.getChart().getXYPlot().getDomainAxis().getLowerBound()+(x-chartPanel.getChartRenderingInfo().getPlotInfo().getDataArea().getX())*chartPanel.getChart().getXYPlot().getDomainAxis().getUpperBound()/domainSize;
			double rangeSize = chartPanel.getChartRenderingInfo().getPlotInfo().getDataArea().getHeight();
			double realValue = chartPanel.getChart().getXYPlot().getRangeAxis().getLowerBound()+(rangeSize-y+chartPanel.getChartRenderingInfo().getPlotInfo().getDataArea().getY())*chartPanel.getChart().getXYPlot().getRangeAxis().getUpperBound()/rangeSize;
			final OptimizerCMAES.ReferencePoint newPoint = new OptimizerCMAES.ReferencePoint(who, (int) Math.round(realTime),realValue);
    	
			final PlotExpData thePlot = this;
			
			for(OptimizerCMAES.ReferencePoint point : pointList){
				if(point.index == newPoint.index && (point.target-newPoint.target)*(point.target-newPoint.target)+(point.time-newPoint.time)*(point.time-newPoint.time) < 50){
					this.pointList.remove(point);
					return;
				}
			}
    	if(newPoint.time<model.Constants.numberOfPoints){
    		this.pointList.add(newPoint);
    	}
    	}
    }
    /**
     * Starting point for the demonstration application.
     *
     * @param args  ignored.
     */
    /*
        /**
     * Creates a sample dataset.
     * 
     * @return A dataset.
     */
    /*
    private XYDataset createSampleDataset() {
        XYSeries series1 = new XYSeries("Series 1");
        series1.add(1.0, 3.3);
        series1.add(2.0, 4.4);
        series1.add(3.0, 1.7);
        XYSeries series2 = new XYSeries("Series 2");
        series2.add(1.0, 7.3);
        series2.add(2.0, 6.8);
        series2.add(3.0, 9.6);
        series2.add(4.0, 5.6);
        XYSeriesCollection dataset = new XYSeriesCollection();
        dataset.addSeries(series1);
        dataset.addSeries(series2);
        return dataset;
    }
    */
    public static void main(final String[] args) {

    	JPanel panel = new JPanel();
		JFrame frame = new JFrame("");
		frame.add(panel);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(1500, 600);
		PlotExpData demo = new PlotExpData("", new double[][]{{1,2},{1,2}}, null);
		panel.add(demo);
		frame.setVisible(true);
		//demo.updatePlot(new double[][]{{1,3},{4,5}}, 1, 1, null);
		demo.repaint();

   }
    
}   
